/**
 * 
 */
package travibot.engine;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import travibot.meta.MaterialType;
import travibot.meta.MetabaseManager;
import travibot.model.Material;
import travibot.model.materials.AbstractMaterial;
import travibot.utility.DescriptionInstantiationException;

/**
 * @author Petr Macek
 * 
 */
public class MaterialFactory implements ApplicationContextAware {

    private static Logger log = Logger.getLogger(MaterialFactory.class);
       
    private ApplicationContext ctx;

    private Map<Class, Material> materialsByClass = new HashMap<Class, Material>();

    private Map<String, Material> materialsByName = new HashMap<String, Material>();
    
    private Map<Integer, Material> materialsById = new HashMap<Integer, Material>();

    private static final String CLASS_UNDEFINED = "Implementing class name not defined for Material {0}";
    private static final String CLASS_NOT_FOUND = "Error finding class {0}";
    private static final String MATERIAL_NOT_FOUND = "Material with name {0} not found";
    private static final String INSTANTIATION_ERROR = "Error instantiating class {0}";

    public Material getMaterial(String name) {

        Material material = materialsByName.get(name);
        if (material == null) {
            initMaterialMaps();
            material = materialsByName.get(name);
            if (material == null) {
                log.error(MessageFormat.format(MATERIAL_NOT_FOUND, name));
            }
        }
        return material;
    }
    
    public Material getMaterial(int id) {

        Material material = materialsById.get(id);
        if (material == null) {
            initMaterialMaps();
            material = materialsById.get(id);
            if (material == null) {
                log.error(MessageFormat.format(MATERIAL_NOT_FOUND, id));
            }
        }
        return material;
    }
    
    public List<Material> getMaterials(){
        
        List<Material> ret = new ArrayList<Material>();
        if(materialsByName.size() == 0){
            initMaterialMaps();
        }
        Set<String> keys = materialsByName.keySet();
        for(String key : keys){
            ret.add(materialsByName.get(key));
        }
        return ret;
    }

    public Material getMaterial(Class clazz) {

        Material material = materialsByClass.get(clazz);
        if (material == null) {
            initMaterialMaps();
            material = materialsByClass.get(clazz);
            if (material == null) {
                log.error(MessageFormat.format(MATERIAL_NOT_FOUND, clazz));
            }
        }
        return material;
    }

    private void initMaterialMaps() {

        List<MaterialType> allTypes = getMetabaseManager().getMaterialTypes();
        for (MaterialType type : allTypes) {
            Material material = getInstance(type);
            materialsByClass.put(material.getClass(), material);
            materialsByName.put(material.getName(), material);
            materialsById.put((Integer)type.getId(), material);
        }
    }

    private static Material getInstance(MaterialType type) {

        Material ret = null;
        Class<Material> clazz = type.getImplementingClass();
        try {            
            if (clazz != null) {                
                ret = clazz.newInstance();
                ((AbstractMaterial)ret).setId(type.getId());
                ((AbstractMaterial)ret).setName(type.getName());
            }
            else {
                throw new DescriptionInstantiationException(MessageFormat.format(CLASS_UNDEFINED, type.getId()));
            }
        }       
        catch (InstantiationException e) {
            throw new DescriptionInstantiationException(MessageFormat.format(INSTANTIATION_ERROR, type
                    .getImplementingClass()), e);
        }
        catch (IllegalAccessException e) {
            throw new DescriptionInstantiationException(e);
        }
        catch (SecurityException e) {
            throw new DescriptionInstantiationException(e);
        }
        catch (IllegalArgumentException e) {
            throw new DescriptionInstantiationException(e);
        }
        return ret;
    }
    

    public void setApplicationContext(ApplicationContext ctx) throws BeansException {

        this.ctx = ctx; 
    }
    
    private MetabaseManager getMetabaseManager(){
        return (MetabaseManager)ctx.getBean("MetaBase");
    }

}
